/*
 * Copyright 2017 JBoss Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.apiman.gateway.engine.vertx.polling.fetchers;

import io.apiman.gateway.engine.vertx.polling.exceptions.BadResponseCodeError;
import io.apiman.gateway.engine.vertx.polling.fetchers.auth.AuthType;
import io.apiman.gateway.engine.vertx.polling.fetchers.auth.Authenticator;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.impl.Arguments;

import java.net.URI;
import java.util.Map;

import org.apache.commons.lang3.EnumUtils;

/**
 * Fetch HTTP and HTTPS resources, with Auth options including BASIC and various OAuth2
 * permutations.
 *
 * <ul>
 *   <li>auth: auth type, one of {@link AuthType}. Otherwise <tt>None</tt> by default.<li>
 * </ul>
 *
 * Refer to {@link AuthType} for available options, such as BASIC, OAuth2, Keycloak, etc.
 *
 * @see AuthType
 * @author Marc Savy {@literal <marc@rhymewithgravy.com>}
 */
@SuppressWarnings("nls")
public class HttpResourceFetcher implements ResourceFetcher {

    private URI uri;
    private boolean isHttps;
    private Vertx vertx;
    private Buffer rawData = Buffer.buffer();
    private Handler<Throwable> exceptionHandler;
    private Map<String, String> config;
    private Authenticator authenticator;

    public HttpResourceFetcher(Vertx vertx, URI uri, Map<String, String> config, boolean isHttps) {
        this.vertx = vertx;
        this.uri = uri;
        this.isHttps = isHttps;
        this.config = config;

        String authString = config.getOrDefault("auth", "NONE").toUpperCase();
        Arguments.require(EnumUtils.isValidEnum(AuthType.class, authString), "auth must be one of: " + AuthType.all());
        authenticator = AuthType.valueOf(authString).getAuthenticator();
        authenticator.validateConfig(config);
    }

    @Override
    public void fetch(Handler<Buffer> resultHandler) {
        int port = uri.getPort();
        if (port == -1) {
            if (isHttps) {
                port = 443;
            } else {
                port = 80;
            }
        }

        HttpClientRequest httpClientRequest = vertx.createHttpClient(new HttpClientOptions().setSsl(isHttps))
            .get(port, uri.getHost(), uri.getPath(), clientResponse -> {
                if (clientResponse.statusCode() / 100 == 2) {
                    clientResponse.handler(data -> {
                        rawData.appendBuffer(data);
                    })
                    .endHandler(end -> resultHandler.handle(rawData))
                    .exceptionHandler(exceptionHandler);
                } else {
                    exceptionHandler.handle(new BadResponseCodeError("Unexpected response code when trying to retrieve config: " //$NON-NLS-1$
                            + clientResponse.statusCode()));
                }
            })
            .exceptionHandler(exceptionHandler);

        authenticator.authenticate(vertx, config, httpClientRequest.headers(), authResult -> {
            if (authResult.succeeded()) {
                // The client request is executed when HttpClientRequest#end is invoked.
                httpClientRequest.end();
            } else {
                exceptionHandler.handle(authResult.cause());
            }
        });
    }

    @Override
    public HttpResourceFetcher exceptionHandler(Handler<Throwable> exceptionHandler) {
        this.exceptionHandler = exceptionHandler;
        return this;
    }
}
